using System.Collections.Generic;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Core
{
    /**
     * An operator for the + operator. Note that this is treated
     * separately from the other 4 arithmetic operators,
     * since + is overloaded to mean string concatenation.
     * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
     */
    internal sealed class AddConcatExpression : Expression
    {
        private readonly Expression _left;
        private readonly Expression _right;

        internal AddConcatExpression(Expression left, Expression right)
        {
            _left = left;
            _right = right;
        }

        protected override ITemplateModel _getAsTemplateModel(Environment env)
        {
            ITemplateModel leftModel = _left.GetAsTemplateModel(env);
            ITemplateModel rightModel = _right.GetAsTemplateModel(env);

            if (leftModel is ITemplateNumberModel && rightModel is ITemplateNumberModel)
            {
                Number first = EvaluationUtil.GetNumber((ITemplateNumberModel) leftModel, _left, env);
                Number second = EvaluationUtil.GetNumber((ITemplateNumberModel) rightModel, _right, env);
                ArithmeticEngine ae =
                    env != null
                        ? env.GetArithmeticEngine()
                        : GetTemplate().GetArithmeticEngine();
                return new SimpleNumber(ae.Add(first, second));
            }

            if (leftModel is ITemplateSequenceModel && rightModel is ITemplateSequenceModel)
            {
                return new ConcatenatedSequence((ITemplateSequenceModel) leftModel, (ITemplateSequenceModel) rightModel);
            }

            try
            {
                string s1 = GetStringValue(leftModel, _left, env) ?? "null";
                string s2 = GetStringValue(rightModel, _right, env) ?? "null";
                return new SimpleScalar(string.Concat(s1, s2));
            }
            catch (NonStringException)
            {
                if (leftModel is ITemplateHashModel && rightModel is ITemplateHashModel)
                {
                    if (leftModel is ITemplateHashModelEx && rightModel is ITemplateHashModelEx)
                    {
                        var leftModelEx = (ITemplateHashModelEx) leftModel;
                        var rightModelEx = (ITemplateHashModelEx) rightModel;
                        if (leftModelEx.Size() == 0)
                        {
                            return rightModelEx;
                        }
                        if (rightModelEx.Size() == 0)
                        {
                            return leftModelEx;
                        }
                        return new ConcatenatedHashEx(leftModelEx, rightModelEx);
                    }
                    return new ConcatenatedHash((ITemplateHashModel) leftModel,
                                                (ITemplateHashModel) rightModel);
                }
                throw;
            }
        }

        internal override bool IsLiteral()
        {
            return ConstantValue != null || (_left.IsLiteral() && _right.IsLiteral());
        }

        protected override Expression DeepCloneImpl(string name, Expression subst)
        {
            return new AddConcatExpression(_left.DeepClone(name, subst), _right.DeepClone(name, subst));
        }

        public override string GetCanonicalForm()
        {
            return _left.GetCanonicalForm() + " + " + _right.GetCanonicalForm();
        }

        private class ConcatenatedSequence : ITemplateSequenceModel
        {
            private readonly ITemplateSequenceModel _left;
            private readonly ITemplateSequenceModel _right;

            internal ConcatenatedSequence(ITemplateSequenceModel left, ITemplateSequenceModel right)
            {
                _left = left;
                _right = right;
            }

            public int Size()
            {
                return _left.Size() + _right.Size();
            }

            public ITemplateModel Get(int i)
            {
                int ls = _left.Size();
                return i < ls ? _left.Get(i) : _right.Get(i - ls);
            }
        }

        private class ConcatenatedHash : ITemplateHashModel
        {
            protected readonly ITemplateHashModel Left;
            protected readonly ITemplateHashModel Right;

            public ConcatenatedHash(ITemplateHashModel left, ITemplateHashModel right)
            {
                Left = left;
                Right = right;
            }

            public ITemplateModel Get(string key)
            {
                ITemplateModel model = Right.Get(key);
                return model ?? Left.Get(key);
            }

            public bool IsEmpty()
            {
                return Left.IsEmpty() && Right.IsEmpty();
            }
        }

        private class ConcatenatedHashEx : ConcatenatedHash, ITemplateHashModelEx
        {
            private CollectionAndSequence _keys;
            private CollectionAndSequence _values;
            private int _size;

            public ConcatenatedHashEx(ITemplateHashModelEx left, ITemplateHashModelEx right)
                : base(left, right)
            {
            }

            public int Size()
            {
                InitKeys();
                return _size;
            }

            public ITemplateCollectionModel Keys()
            {
                InitKeys();
                return _keys;
            }

            public ITemplateCollectionModel Values()
            {
                InitValues();
                return _values;
            }

            private void InitKeys()
            {
                if (_keys == null)
                {
                    var keySet = new HashSet<string>();
                    var keySeq = new SimpleSequence(32);
                    AddKeys(keySet, keySeq, (ITemplateHashModelEx) Left);
                    AddKeys(keySet, keySeq, (ITemplateHashModelEx) Right);
                    _size = keySet.Count;
                    _keys = new CollectionAndSequence(keySeq);
                }
            }

            private static void AddKeys(HashSet<string> set, SimpleSequence keySeq, ITemplateHashModelEx hash)
            {
                ITemplateModelIterator it = hash.Keys().Iterator();
                while (it.HasNext())
                {
                    var tsm = (ITemplateScalarModel)it.Next();
                    if (set.Add(tsm.GetAsString()))
                    {
                        // The first occurence of the key decides the index;
                        // this is consisten with stuff like java.util.LinkedHashSet.
                        keySeq.Add(tsm);
                    }
                }
            }

            private void InitValues()
            {
                if (_values == null)
                {
                    var seq = new SimpleSequence(Size());
                    // Note: size() invokes initKeys() if needed.

                    int ln = _keys.Size();
                    for (int i = 0; i < ln; i++)
                    {
                        seq.Add(Get(((ITemplateScalarModel)_keys.Get(i)).GetAsString()));
                    }
                    _values = new CollectionAndSequence(seq);
                }
            }
        }
    }
}